function FiniteStateMachine(unit) {
	this.unit = unit;

	this.statesEnum = {
		STAND:10,
		WALK_RIGHT:13,
		WALK_LEFT:16,
		CROUCH:18,
		PUNCH:20,
		SIMPLE_KICK:30,
		ROUNDHOUSE_KICK:40,
		STAFF_KATA:50,
		HELI_KICK:60,
		STAFF_SWIRL:70
	};
	
	this.states = new Array();
	this.states[this.statesEnum.STAND] = new StandState(this.unit);
	this.states[this.statesEnum.WALK_RIGHT] = new WalkRightState(this.unit);
	this.states[this.statesEnum.WALK_LEFT] = new WalkLeftState(this.unit);
	this.states[this.statesEnum.CROUCH] = new CrouchState(this.unit);
	this.states[this.statesEnum.PUNCH] = new PunchState(this.unit);
	this.states[this.statesEnum.SIMPLE_KICK] = new SimpleKickState(this.unit);
	this.states[this.statesEnum.ROUNDHOUSE_KICK] = new RoundhouseKickState(this.unit);
	this.states[this.statesEnum.STAFF_KATA] = new StaffKataState(this.unit);
	this.states[this.statesEnum.HELI_KICK] = new HeliKickState(this.unit);
	this.states[this.statesEnum.STAFF_SWIRL] = new StaffSwirlState(this.unit);
	
	this.COMBOS = [
	    {
			actions:[this.states[this.statesEnum.PUNCH], 
			         this.states[this.statesEnum.SIMPLE_KICK], 
			         this.states[this.statesEnum.SIMPLE_KICK]],
			state:this.states[this.statesEnum.ROUNDHOUSE_KICK]
	    },
	    {
			actions:[this.states[this.statesEnum.SIMPLE_KICK],
			         this.states[this.statesEnum.SIMPLE_KICK], 
			         this.states[this.statesEnum.PUNCH], 
			         this.states[this.statesEnum.SIMPLE_KICK]],
			state:this.states[this.statesEnum.HELI_KICK]
	    }
	];
	
	var SPECIAL_MOVES = [
		{
			actions:[INPUT_EVENT_TYPE.down, INPUT_EVENT_TYPE.right, INPUT_EVENT_TYPE.kick],
			state:this.statesEnum.STAFF_KATA
		},{
			actions:[INPUT_EVENT_TYPE.right, INPUT_EVENT_TYPE.down, INPUT_EVENT_TYPE.left, INPUT_EVENT_TYPE.punch],
			state:this.statesEnum.STAFF_SWIRL
		} 
	];
	
	//key presses history. For special moves:
	this.actionsHistory = [];
	//states history. For combos:
	this.statesHistory = [];
	
	this.currentState = this.states[this.statesEnum.STAND];
	this.currentState.baseState_init();
	
	this.onUnitProcessInput = function(inputEvent) {
		if( (inputEvent.type == INPUT_EVENT_TYPE.down || 
				inputEvent.type == INPUT_EVENT_TYPE.left || 
				inputEvent.type == INPUT_EVENT_TYPE.right) &&
				inputEvent.state == INPUT_EVENT_STATE.end) {
			//if it's a key up on a motion action (we're ending a motion), don't register/equeue it
			//this way players are not forced to hold down all directions while pressing the hit key for a special move in order to do it
		} else {
			this.actionsHistory.push(inputEvent);
			while(this.actionsHistory.length > USER_INPUT_BUFFER_CAPACITY) {
				this.actionsHistory.shift();//remove oldest input event
			}
		}
			
		this.currentState.onUnitProcessInput(inputEvent);
	};
	
	this.onUnitUpdateState = function() {		
		var nextState = this.currentState.onUnitUpdateState();
		//when switching between naturally occurring states... :
		if(nextState !==  this.currentState) {
			//check first if we're not in a special move situation...
			var specialMoveState = this.checkSpecialMovesState();	
			if(specialMoveState != null) {
				//if actions history brought us to a special move state, go to it right now (override the next naturally occurring state)
				this.actionsHistory = [];
				nextState = this.states[specialMoveState];
			} /* else {
				//if there's no special move situation, use the next naturally occurring state
			*/
			
			//before init-ing the next state, put the one we just ended into the state history:
			if(this.currentState !== this.states[this.statesEnum.STAND]) {
				this.statesHistory.push(this.currentState);
				while(this.statesHistory.length > STATES_HISTORY_CAPACITY) {
					this.statesHistory.shift();//remove oldest state
				}
			}
			
			nextState.baseState_init();
		}
		this.currentState = nextState;				
	};
	
	this.checkSpecialMovesState = function() {
		for(var i = 0; i < SPECIAL_MOVES.length; i++) {
			var specialMove = SPECIAL_MOVES[i];
			//if the current actions history doesn't have enough key presses for this move, skip it:
			if(this.actionsHistory.length < specialMove.actions.length) {
				continue;
			}
			var matchesSpecialMove = true;
			//check if the last n key presses are those for this special move:
			for(var j = 0; j < specialMove.actions.length; j++) {
				if(specialMove.actions[specialMove.actions.length-1-j] !== this.actionsHistory[this.actionsHistory.length-1-j].type) {
					matchesSpecialMove = false;
					break;
				}
			}
			//if last key presses are those for this move, check that they were pressed fast enought:
			if(matchesSpecialMove) {
				var startTimeStamp =  this.actionsHistory[this.actionsHistory.length-specialMove.actions.length+1].timeStamp;
				var endTimeStamp =  this.actionsHistory[this.actionsHistory.length-1].timeStamp;
				//let's not get fancy, we're just checking that average time between actions is short enough:
				if((endTimeStamp-startTimeStamp)/(specialMove.actions.length-1) <= 140) {
					return specialMove.state;
				}
			}
		}
		return null;
	};
}

function BaseState(unit) {
	this.unit = unit;
	
	this.baseState_init = function() {
		this.init();
	};
	
	this.init = function() {};
	
	this.onUnitProcessInput = function(inputEvent) {};
	
	this.onUnitUpdateState = function() {};
	
	this.getState = function(stateEnum) {
		var state = this.unit.finiteStateMachine.states[stateEnum];
		return state;
	};
	
	//TODO: generate these functions programmatically:	
	this.getStandState = function() {
		return this.getState(this.unit.finiteStateMachine.statesEnum.STAND); 
	};
	
	this.getWalkRightState = function() {
		return this.getState(this.unit.finiteStateMachine.statesEnum.WALK_RIGHT); 
	};
	
	this.getWalkLeftState = function() {
		return this.getState(this.unit.finiteStateMachine.statesEnum.WALK_LEFT); 
	};
	
	this.getCrouchState = function() {
		return this.getState(this.unit.finiteStateMachine.statesEnum.CROUCH); 
	};
	
	this.getPunchState = function() {
		return this.getState(this.unit.finiteStateMachine.statesEnum.PUNCH);
	};
	
	this.getSimpleKickState = function() {
		return this.getState(this.unit.finiteStateMachine.statesEnum.SIMPLE_KICK);
	};

	this.getRoundhouseKickState = function() {
		return this.getState(this.unit.finiteStateMachine.statesEnum.ROUNDHOUSE_KICK);
	};
	
	this.getStaffKataState = function() {
		return this.getState(this.unit.finiteStateMachine.statesEnum.STAFF_KATA);
	};
}

function BaseMoveState(unit, animation, inputEventType) {
	$.extend(this,new BaseState(unit));
	
	this.stateAnimation = animation;
	this.stateInputEventType = inputEventType;
	
	this.nextState = null;
	
	this.init = function() {
		this.unit.currentAnimation = this.stateAnimation;		
		this.unit.currentAnimation.start();
	};
	
	/*
	 * Move states, such as Stand, Walk Left/Right, should switch to a new state immediately upon new input from user.
	 * This is NOT true for Hit States, such as Punch, SimpleKick, RoundhouseKick, etc. where once the state started, it will NOT switch 
	 * to a new state until its animation is over
	 */
	this.onUnitProcessInput = function(inputEvent) {
		if(this.stateInputEventType != null && inputEvent.type == this.stateInputEventType && inputEvent.state == INPUT_EVENT_STATE.end) {
			this.nextState = this.getStandState();
		} else if(inputEvent.type == INPUT_EVENT_TYPE.punch) {
			this.nextState = this.getPunchState();
		} else if(inputEvent.type == INPUT_EVENT_TYPE.kick) {
			this.nextState = this.getSimpleKickState();
		} else if(inputEvent.type == INPUT_EVENT_TYPE.right && inputEvent.state == INPUT_EVENT_STATE.start) {
			this.nextState = this.getWalkRightState();
		} else if(inputEvent.type == INPUT_EVENT_TYPE.left && inputEvent.state == INPUT_EVENT_STATE.start) {
			this.nextState = this.getWalkLeftState();
		} else if(inputEvent.type == INPUT_EVENT_TYPE.down && inputEvent.state == INPUT_EVENT_STATE.start) {
			this.nextState = this.getCrouchState();
		}
	};
}

function StandState(unit) {
	
	$.extend(this,new BaseMoveState(unit, ANIMATION_MANAGER.createStandAnimation(), null));
	
	this.onUnitUpdateState = function() {
		if(this.nextState !=null) {
			var tmp = this.nextState;
			this.nextState = null;
			return tmp;
		} else {
			return this;
		}
	};
}

function BaseWalkState(unit, direction, inputEventType) {
	
	$.extend(this,new BaseMoveState(unit, ANIMATION_MANAGER.createWalkAnimation(), inputEventType));
	
	this.direction = direction;
	
	this.onUnitUpdateState = function() {
		if(this.nextState !=null) {
			var tmp = this.nextState;
			this.nextState = null;
			return tmp;
		} else {
			var newX = this.unit.startX + this.direction * WALK_SPEED_PIXELS_PER_UPDATE;
			if(newX >= 10 && newX <= CANVAS_WIDTH-100) {
				this.unit.startX = newX;
			}			
			return this;
		}
	};
}

function WalkRightState(unit) {
	
	$.extend(this,new BaseWalkState(unit, 1, INPUT_EVENT_TYPE.right));
}

function WalkLeftState(unit) {
	
	$.extend(this,new BaseWalkState(unit, -1, INPUT_EVENT_TYPE.left));
}

function CrouchState(unit) {
	
	$.extend(this,new BaseMoveState(unit, ANIMATION_MANAGER.createCrouchAnimation(), INPUT_EVENT_TYPE.down));
		
	this.onUnitUpdateState = function() {
		if(this.nextState !=null) {
			var tmp = this.nextState;
			this.nextState = null;
			return tmp;
		} else {
			return this;
		}
	};
}

function SimpleHitBaseState(unit, animation) {
	
	$.extend(this,new BaseState(unit));
	this.animation = animation;
	
	this.init = function() {
		this.nextState = this.getStandState();
		this.unit.currentAnimation = this.animation;
		this.unit.currentAnimation.start();
	};
	
	this.onUnitUpdateState = function() {
		var comboState = this.checkCombosState();
		if(comboState != null) {
			return comboState;
		}	
		if(unit.currentAnimation.isOver()) {
			return this.getStandState();			 			
		} else {
			return this;
		}
	};
	
	//this function should be way more generic, and be able to check for any COMBOS:
	this.checkCombosState = function() {
		var statesHistory = this.unit.finiteStateMachine.statesHistory;
		var statesHistoryLength = statesHistory.length;
		
		for(var i = 0; i < this.unit.finiteStateMachine.COMBOS.length; i++) {
			var combo = this.unit.finiteStateMachine.COMBOS[i];
			var comboStatesLength = combo.actions.length;
			if(statesHistoryLength < comboStatesLength-1) {
				//not enough actions were made to match this combo
				continue;
			}
			if(combo.actions[comboStatesLength-1] !== this) {
				//if this combo doesn't end with this state skip it
				continue;
			}
			
			var matchesCombo = true;
			
			//check if the last n-1 states BEFORE THIS ONE are those for this combo:
			for(var j = 1; j <= comboStatesLength-1; j++) {
				if(combo.actions[comboStatesLength-1-j] !== statesHistory[statesHistoryLength-j]) {
					matchesCombo = false;
					break;
				}
			}
			
			if(matchesCombo) {
				//TODO: check if combo was executed fast enough
				return combo.state;
			}
		}
		return null;
	};
	
}

function PunchState(unit) {
	
	$.extend(this,new SimpleHitBaseState(unit, ANIMATION_MANAGER.createPunchAnimation()));
}

function SimpleKickState(unit) {
	
	$.extend(this,new SimpleHitBaseState(unit, ANIMATION_MANAGER.createSimpleKickAnimation()));
}

function SpecialHitState(unit, animation) {
	
	$.extend(this,new BaseState(unit));
	
	this.stateAnimation = animation;
	
	this.init = function() {
		this.unit.currentAnimation = this.stateAnimation;
		this.unit.currentAnimation.start();
	};
	
	this.onUnitUpdateState = function() {
		if(unit.currentAnimation.isOver()) {
			this.unit.finiteStateMachine.statesHistory = [];
			return this.getStandState();
		} else {
			return this;
		}
	};
}

function RoundhouseKickState(unit) {
	
	$.extend(this,new SpecialHitState(unit, ANIMATION_MANAGER.createRoundhouseKickAnimation()));
}

function StaffKataState(unit) {
	
	$.extend(this,new SpecialHitState(unit, ANIMATION_MANAGER.createStaffKataAnimation()));;
}

function HeliKickState(unit) {
	
	$.extend(this,new SpecialHitState(unit, ANIMATION_MANAGER.createHeliKickAnimation()));;
}

function StaffSwirlState(unit) {
	
	$.extend(this,new SpecialHitState(unit, ANIMATION_MANAGER.createStaffSwirlAnimation()));;
}